// Copyright (c) 2022, the Dart project authors.  Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.

import 'dart:ffi' as ffi;
import 'dart:io';
import 'dart:isolate';

import 'package:ffi/ffi.dart';
import 'package:meta/meta.dart' show internal;
import 'package:path/path.dart';
import 'napi_bridge_lib.dart';
import 'errors.dart';
import 'arkreference.dart';
// We provide minimal typedefs in types.dart; no ffigen-generated bindings here.
import 'types.dart';

// Napi versions
enum NapiVersions {
  VERSION_1_0(0x00010000),
  VERSION_1_1(0x00010001),
  VERSION_1_2(0x00010002),
  VERSION_1_3(0x00010003),
  VERSION_1_4(0x00010004),
  VERSION_1_5(0x00010005),
  VERSION_1_6(0x00010006);

  const NapiVersions(this.value);
  final int value;
}

String _getLibraryFileName(String base) {
  if (Platform.isLinux || Platform.isAndroid) {
    return 'lib$base.so';
  } else if (Platform.isWindows) {
    return '$base.dll';
  } else if (Platform.isMacOS) {
    return 'lib$base.dylib';
  } else {
    throw UnsupportedError('Cannot derive library name: unsupported platform');
  }
}

/// Loads dartnapi helper library.
ffi.DynamicLibrary _loadDartNapiLibrary({String? dir, String baseName = 'dartnapi'}) {
  var fileName = _getLibraryFileName(baseName);
  if (!Platform.isAndroid) {
    if (dir != null) {
      fileName = join(dir, fileName);
    }
    final file = File(fileName);
    if (!file.existsSync()) {
      throw HelperNotFoundError(fileName);
    }
  }
  try {
    return ffi.DynamicLibrary.open(fileName);
  } catch (_) {
    throw DynamicLibraryLoadError(fileName);
  }
}

/// Utilities to spawn and manage Napi.
abstract final class Napi {
  static final ffi.DynamicLibrary _dylib = _loadDartNapiLibrary(dir: _dylibDir);
  // Note: We no longer instantiate NapiBindings here.

  /// Store dylibDir if any was used.
  static String _dylibDir = join('build', 'napi_libs');

  /// Sets the directory where dynamic libraries are looked for.
  /// On dart standalone, call this in new isolate before doing
  /// any Napi operation.
  ///
  /// (The reason is that dylibs need to be loaded in every isolate.
  /// On flutter it's done by library. On dart standalone we don't
  /// know the library path.)
  static void setDylibDir({required String dylibDir}) {
    if (!Platform.isOhos) {
      _dylibDir = dylibDir;
    }
  }

  /// Spawn an instance of ArkTS using Napi. This method should be called at the
  /// beginning of the program with appropriate options, before other isolates
  /// are spawned.
  ///
  /// [dylibDir] is path of the directory where the wrapper library is found.
  /// This parameter needs to be passed manually on __Dart standalone target__,
  /// since we have no reliable way to bundle it with the package.
  ///
  /// [arktsOptions], [ignoreUnrecognized], & [napiVersion] are passed to the ArkTS.
  /// Strings in [classPath], if any, are used to construct an additional
  /// JVM option of the form "-Djava.class.path={paths}".
  static void spawn({
    String? dylibDir,
    List<String> arktsOptions = const [],
    List<String> classPath = const [],
    bool ignoreUnrecognized = false,
    NapiVersions napiVersion = NapiVersions.VERSION_1_6,
  }) {
    final status = spawnIfNotExists(
      dylibDir: dylibDir,
      arktsOptions: arktsOptions,
      classPath: classPath,
      ignoreUnrecognized: ignoreUnrecognized,
      napiVersion: napiVersion,
    );
    if (status == false) {
      throw NapiVmExistsError();
    }
  }

  /// Same as [spawn] but if an ArkTS instance exists, returns silently instead of
  /// throwing [NapiVmExistsError].
  ///
  /// If the options are different than that of existing VM, the existing VM's
  /// options will remain in effect.
  /// Initializes Napi environment.
  ///
  /// In HarmonyOS Napi, the JSVM is managed by the system,
  /// so we only need to ensure the environment is properly initialized.
  static bool initializeNapiEnvironment({
    String? dylibDir,
    List<String> arktsOptions = const [],
    List<String> classPath = const [],
    bool ignoreUnrecognized = false,
    NapiVersions napiVersion = NapiVersions.VERSION_1_6,
  }) {
    try {
      // Load the dartnapi library to access C bridge functions
      final library = _loadDartNapiLibrary();
      
      // Look up the initialization function
      final initFunc = library.lookupFunction<
          ffi.Int32 Function(),
          int Function()>('dartnapi_init_default_env');
      
      // Initialize the environment
      final result = initFunc();
      return result == 0;
    } catch (e) {
      // If we can't initialize, try to get the current environment
      try {
        final env = _fetchGlobalEnv();
        return env != ffi.Pointer.fromAddress(0);
      } catch (e2) {
        return false;
      }
    }
  }

  // Note: In HarmonyOS Napi, we don't need VM initialization arguments
  // like in JNI, since the JSVM is managed by the system

  /// Returns the current Napi environment.
  ///
  /// In HarmonyOS Napi, we don't have direct access to the JSVM like JNI's JavaVM.
  /// Instead, we work with napi_env which provides access to the JavaScript runtime.
  napi_env getNapiEnvironment() {
    // In Napi, the environment is provided by the host application
    // We need to get it from the current context
    // For now, return a placeholder - this should be implemented based on
    // how the host application provides the napi_env
    return ffi.Pointer.fromAddress(0);
  }

  /// Gets a JavaScript constructor function by name.
  ///
  /// In HarmonyOS Napi, we don't have "classes" like in Java/JNI.
  /// Instead, we work with JavaScript constructor functions and objects.
  /// This method gets a constructor function from the global scope.
  static napi_value getConstructor(String name) {
    // In Napi, we get constructor functions from the global object
    // This is different from JNI's class loading
    // For now, return a placeholder - this should be implemented using
    // napi_get_named_property on the global object
    return ffi.Pointer.fromAddress(0);
  }

  /// Finds the class from its [name].
  ///
  /// This is a compatibility method for JNI-style class finding.
  /// In Napi, this actually gets a constructor function.
  static NapiClassPtr findClass(String name) {
    // For compatibility with JNI-style code, we return a placeholder
    // In actual implementation, this should get the constructor function
    return ffi.Pointer.fromAddress(0);
  }

  /// Same as [spawn] but if an ArkTS instance exists, returns silently instead of
  /// throwing [NapiVmExistsError].
  ///
  /// This is a compatibility method for JNI-style VM management.
  /// In Napi, we use initializeNapiEnvironment instead.
  static bool spawnIfNotExists({
    String? dylibDir,
    List<String> arktsOptions = const [],
    List<String> classPath = const [],
    bool ignoreUnrecognized = false,
    NapiVersions napiVersion = NapiVersions.VERSION_1_6,
  }) {
    return initializeNapiEnvironment(
      dylibDir: dylibDir,
      arktsOptions: arktsOptions,
      classPath: classPath,
      ignoreUnrecognized: ignoreUnrecognized,
      napiVersion: napiVersion,
    );
  }

  /// Throws an exception.
  // TODO(#561): Throw an actual `NapiThrowable`.
  @internal
  static void throwException(NapiThrowablePtr exception) {
    // In Napi, exception handling is different from JNI
    // For now, throw a generic exception
    throw NapiException('Napi exception occurred', '');
  }

  /// Returns the current napi_env pointer (opaque).
  static ffi.Pointer<ffi.Void> _fetchGlobalEnv() {
    // In HarmonyOS Napi, we need to get the napi_env from the current context
    // This requires a C bridge function that can access the current napi_env
    try {
      final napiEnvPtr = NapiBridgeLib.load().getCurrentEnv();
      
      if (napiEnvPtr == ffi.Pointer.fromAddress(0)) {
        throw NapiException('Failed to get current napi_env', '');
      }
      return napiEnvPtr;
    } catch (e) {
      // 提示端侧需在 ETS 中调用 dartnapi_set_env_from_ets 注入 env
      throw NapiException(
          'napi_env is null. Please call dartnapi_set_env_from_ets(env) in ETS before using Dart side.',
          e.toString());
    }
  }

  // Deprecated: GlobalNapiEnv indirection is not used for HarmonyOS Napi.

  /// Returns current application context on HarmonyOS.
  static ArkReference getCachedApplicationContext() {
    // HarmonyOS doesn't have the same concept as Android's ApplicationContext
    // For now, return a placeholder
    return ArkGlobalReference(ffi.Pointer.fromAddress(0));
  }

  /// Returns current activity on HarmonyOS.
  static ArkReference getCurrentActivity() {
    // HarmonyOS doesn't have the same concept as Android's Activity
    // For now, return a placeholder  
    return ArkGlobalReference(ffi.Pointer.fromAddress(0));
  }

  /// Get the initial classLoader of the application.
  ///
  /// This is especially useful on HarmonyOS, where
  /// Napi threads cannot access application classes using
  /// the usual `NapiEnv.FindClass` method.
  static ArkReference getApplicationClassLoader() {
    // HarmonyOS doesn't have the same concept as Android's ClassLoader
    // For now, return a placeholder
    return ArkGlobalReference(ffi.Pointer.fromAddress(0));
  }
}

/// Extensions for use by `napi` generated code.
@internal
extension ProtectedNapiExtensions on Napi {
  static bool _initialized = false;

  /// Initializes Napi runtime for HarmonyOS.
  static void ensureInitialized() {
    if (!_initialized) {
      // In Napi, initialization is different from JNI
      // For now, assume successful initialization
      _initialized = true;
    }
  }

  static int getCurrentIsolateId() {
    // In Napi, isolate management is different
    // For now, return a placeholder
    return 0;
  }

  // Placeholder: Throwable type
  // ignore: unused_field
  static final _napiThrowableClass = 'Throwable';

  /// Returns a new DartException.
  static ffi.Pointer<ffi.Void> newDartException(Object exception) {
    // In Napi, exception creation is different
    // For now, return a placeholder
    return ffi.Pointer.fromAddress(0);
  }

  /// Returns a new PortContinuation.
  static ArkReference newPortContinuation(ReceivePort port) {
    ensureInitialized();
    // In Napi, port continuation is different
    // For now, return a placeholder
    return ArkGlobalReference(ffi.Pointer.fromAddress(0));
  }

  /// Returns the result of a callback.
  static void returnResult(
      ffi.Pointer<ffi.Void> result, NapiObjectPtr object) async {
    // The result is `nullptr` when the callback is a listener.
    if (result != ffi.Pointer.fromAddress(0)) {
      // In Napi, result handling is different
      // For now, no-op
    }
  }

  static ffi.Pointer<ffi.Void> newNapiObjectFinalizableHandle(
    Object object,
    ffi.Pointer<ffi.Void> reference,
    NapiObjectRefType refType,
  ) {
    ensureInitialized();
    // In Napi, finalizable handle creation is different
    // For now, return a placeholder
    return ffi.Pointer.fromAddress(0);
  }

  static ffi.Pointer<ffi.Void> newBooleanFinalizableHandle(
    Object object,
    ffi.Pointer<ffi.Bool> reference,
  ) {
    ensureInitialized();
    // In Napi, boolean finalizable handle creation is different
    // For now, return a placeholder
    return ffi.Pointer.fromAddress(0);
  }

  static void deleteFinalizableHandle(
      ffi.Pointer<ffi.Void> finalizableHandle, Object object) {
    ensureInitialized();
    // In Napi, finalizable handle deletion is different
    // For now, no-op
  }

  static ffi.Pointer<T> Function<T extends ffi.NativeType>(String) get lookup =>
      Napi._dylib.lookup;
}

extension AdditionalEnvMethods on Napi {
  /// Convenience method for converting a [NapiStringPtr] to dart string.
  /// if [releaseOriginal] is specified, nstring passed will be deleted using
  /// DeleteGlobalRef.
  String toDartString(NapiStringPtr nstringPtr, {bool releaseOriginal = false}) {
    if (nstringPtr == ffi.Pointer.fromAddress(0)) {
      throw NapiNullError();
    }
    // TODO: 使用 napi_get_value_string_utf16 获取实际字符串
    // 先返回占位，确保可编译
    return '';
  }

  /// Returns a new [NapiStringPtr] from contents of [s] for HarmonyOS Napi.
  NapiStringPtr toNapiStringPtr(String s) {
    // TODO: 使用 napi_create_string_utf16 创建 napi_value
    // 先返回占位，确保可编译
    return ffi.Pointer.fromAddress(0);
  }
}

@internal
extension StringMethodsForNapi on String {
  /// Returns a Utf-8 encoded `Pointer<Char>` with contents same as this string.
  ffi.Pointer<ffi.Char> toNativeChars(ffi.Allocator allocator) {
    return toNativeUtf8(allocator: allocator).cast<ffi.Char>();
  }
}

